#include "ota_admin.h"
#include "string.h"
#include "http_request.h"

#define OTA_ADMIN_STATE_IDLE 0
#define OTA_ADMIN_STATE_RUN 1

#ifdef CONFIG_SPIRAM
#define _malloc_(size) heap_caps_malloc(size, MALLOC_CAP_SPIRAM)
#else
#define _malloc_(size) malloc(size)
#endif

#define log_i(...)           \
    do                       \
    {                        \
        printf("[I] ");      \
        printf(__VA_ARGS__); \
        printf("\r\n");      \
    } while (0)
#define log_e(...)           \
    do                       \
    {                        \
        printf("[E] ");      \
        printf(__VA_ARGS__); \
        printf("\r\n");      \
    } while (0)

const char *const ota_admin_get_status_str(uint16_t status)
{
    switch (status)
    {
    case OTA_ADMIN_STATUS_OK:
        return "已完成";
    case OTA_ADMIN_STATUS_ERROR_PARAMETERS:
        return "参数错误: 参数不完整";
    case OTA_ADMIN_STATUS_ERROR_NO_AUTH:
        return "设备没有被授权";
    case OTA_ADMIN_STATUS_ERROR_NO_FRAME:
        return "当前设备所属项目没有绑定固件";
    case OTA_ADMIN_STATUS_ERROR_NO_FIND_FRAME:
        return "服务器出错: 没有找到固件信息";
    case OTA_ADMIN_STATUS_ERROR_NO_VERSION:
        return "当前设备所属项目绑定的固件没有发布过版本";
    case OTA_ADMIN_STATUS_ERROR_NO_FIND_FRAME_BIN:
        return "当前设备所属项目绑定的固件没有创建过版本信息";
    case OTA_ADMIN_STATUS_ERROR_NO_FIND_TEST:
        return "当前设备所属项目绑定的固件没有创建过测试版本信息";
    case OTA_ADMIN_STATUS_ERROR_VERSION:
        return "参数 version 格式错误";
    case OTA_ADMIN_STATUS_ERROR_NO_PARTITION:
        return "参数 partition 错误, 没有指定的分区";
    case OTA_ADMIN_STATUS_ERROR_NO_FIND_FILE:
        return "服务器出错: 没有找到固件分区文件, 疑似丢失";
    case OTA_ADMIN_STATUS_ERROR_TIMEOUT:
        return "参数 key 超过 服务器指定最大范围时间, 请同步服务器时间";
    case OTA_ADMIN_STATUS_ERROR_NO_PROJECT:
        return "不存在项目ID";
    case OTA_ADMIN_STATUS_ERROR_CHECK_FAILED:
        return "通讯帧密钥校验失败";
    case OTA_ADMIN_STATUS_OK_NO_NEED_TO_UPDATE:
        return "当前设备版本已是最新版本, 无需更新";
    }
    return "未定义错误";
}
typedef struct ota_admin
{
    ota_admin_data_s data;
    char mac[33];          /* 16byte */
    uint32_t last_get_key; /* 上次获取key的时间 */
    uint32_t last_time;    /* 上次同步的时间 */
    uint64_t key;          /* 与服务器同步的时间 */
    uint8_t state : 2;     /* 运行状态 */
    uint8_t auth : 1;      /* 是否已连接 */
    uint8_t get_test : 1;  /* 是否获取测试版本 */
    uint8_t err;           /* 错误状态 */
    char md5_token[33];
    char buffer[256];
    char str_buf[];
} ota_admin_s, *ota_admin_t;

/* 1. Get Server Timestamp */
#include "esp_rom_md5.h"
#include "file_tool.h"
static uint8_t http_get_key_cb(void *user_data, uint8_t *data, uint32_t size)
{
    if (!size)
        return 2;
    char *p = strstr((char *)data, "\"msg\":");
    if (p)
    {
        p += 6;
        char *end = strrchr(p, '}');
        if (end)
            *end = 0;
        ota_admin_t admin = (ota_admin_t)user_data;
        admin->key = atoll(p);
        if (admin->key > 0)
        {
            admin->last_get_key = ota_admin_port_get_time();
            /* 刷新md5_token */
            uint8_t md5[16] = {0};
            snprintf(admin->buffer, sizeof(admin->buffer), "%s|%s|%llu", admin->data.token, admin->data.id, admin->key);
            md5_context_t context;
            esp_rom_md5_init(&context);
            esp_rom_md5_update(&context, (unsigned char *)admin->buffer, strlen(admin->buffer));
            esp_rom_md5_final(md5, &context);
            strcpy(admin->md5_token, get_hex_str(md5, 16));
            return 0;
        }
    }
    log_e("Failed to get key: %s", (char *)data);
    return 2;
}
static uint8_t ota_admin_get_time(ota_admin_t admin)
{
    if (!admin->last_get_key || admin->last_get_key + OTA_ADMIN_TIMEOUT >= ota_admin_port_get_time())
    {
        sniprintf(admin->buffer, sizeof(admin->buffer), "%s/api/v1/device/timestamp", admin->data.url);
        return http_request_from_url("POST", admin->buffer, NULL, 0, http_get_key_cb, admin, false) == 0;
    }
    else
        return 1;
}

/* 2. Get Device Auth Status */
static uint8_t http_get_auth_cb(void *user_data, uint8_t *data, uint32_t size)
{
    if (!size)
        return 2;
    char *p = strstr((char *)data, "\"msg\":");
    uint8_t err = OTA_ADMIN_STATUS_OK;
    if (p)
    {
        p += 7;
        ota_admin_t admin = (ota_admin_t)user_data;
        if (!strncmp(p, "OK", 2))
        {
            log_i("Auth OK");
            admin->auth = 1;
            return 0;
        }
        else
        {
            char *err_str = strstr((char *)data, "\"code\":");
            if (err_str)
            {
                err_str += 7;
                err = atoi(err_str);
            }
        }
    }
    log_e("Auth Error: %s, %d", (char *)data, err);
    return 2;
}

static uint8_t ota_admin_get_auth(ota_admin_t admin)
{
    uint16_t url_len = snprintf(admin->buffer, sizeof(admin->buffer), "%s/api/v1/device/auth", admin->data.url);
    char *json_data = &admin->buffer[url_len + 1];
    char *ip = ota_admin_port_get_ip();
    uint16_t data_len = 0;
    data_len = snprintf(
        json_data, sizeof(admin->buffer) - url_len,
        "{\"id\":\"%s\",\"token\":\"%s\",\"mac\":\"%s\",\"key\":%llu, \"version\":\"v%s\"",
        admin->data.id, admin->md5_token, admin->mac, admin->key, admin->data.version);
    if (ip)
    {
        data_len += snprintf(
            &json_data[data_len], sizeof(admin->buffer) - url_len - data_len,
            ", \"ip\":\"%s\"",
            ip);
    }
    data_len += snprintf(&json_data[data_len], sizeof(admin->buffer) - url_len - data_len, "}");
    return http_request_from_url("POST", admin->buffer, json_data, data_len, http_get_auth_cb, admin, false) == 0;
}

/* 3. Check Version */
static uint8_t http_get_version_cb(void *user_data, uint8_t *data, uint32_t size)
{
    if (!size)
        return 2;
    char *p = strstr((char *)data, "\"msg\":\"v");
    if (p)
    {
        ota_admin_t admin = (ota_admin_t)user_data;
        p += 8;
        p[1] = '\0';
        uint8_t version_master = atoi(p);
        p[3] = '\0';
        uint8_t version_sub = atoi(&p[2]);
        log_i("当前版本:v%s", admin->data.version);
        log_i("获取版本:v%d.%d", version_master, version_sub);
        if (version_master >= (admin->data.version_num >> 8))
        {
            if (version_master == (admin->data.version_num >> 8))
            {
                if (version_sub <= (admin->data.version_num & 0xFF))
                    goto http_get_version_cb_is_new;
            }
            log_i("应用可更新");
            return 0;
        }
    http_get_version_cb_is_new:
        log_i("应用已是最新版本");
        return 2;
    }
    log_e("Get Version Error: %s", (char *)data);
    return 2;
}
static uint8_t ota_admin_get_version(ota_admin_t admin)
{
    uint16_t url_len = sniprintf(admin->buffer, sizeof(admin->buffer), "%s/api/v1/device/version", admin->data.url);
    char *json_data = &admin->buffer[url_len + 1];
    uint16_t data_len = snprintf(json_data, sizeof(admin->buffer) - url_len, "{\"id\":\"%s\",\"token\":\"%s\",\"mac\":\"%s\",\"key\":%llu%s}",
                                 admin->data.id, admin->md5_token, admin->mac, admin->key, admin->get_test ? ",\"test\":1" : "");
    return http_request_from_url("POST", admin->buffer, json_data, data_len, http_get_version_cb, admin, false);
}

static uint8_t ota_admin_connect(ota_admin_t admin)
{
    if (ota_admin_get_time(admin) && ota_admin_get_auth(admin))
        return 1;
    return 0;
}

void ota_admin_loop(void *admin_handle)
{
    if (!admin_handle)
        return;
    ota_admin_t admin = (ota_admin_t)admin_handle;
    if (!admin->data.id || !admin->data.token || !strnlen(admin->mac, sizeof(admin->mac)))
        return;
    if (admin->last_time && admin->last_time + OTA_ADMIN_TIMEOUT < ota_admin_port_get_time())
        return;
    admin->last_time = ota_admin_port_get_time();
    if (!admin->auth)
    {
        ota_admin_connect(admin);
        return;
    }
    ota_admin_get_version(admin);
}

void ota_admin_get_test(void *admin_handle, uint8_t enable)
{
    if (!admin_handle)
        return;
    ota_admin_t admin = (ota_admin_t)admin_handle;
    admin->get_test = enable;
}

void ota_admin_init(void *admin_handle, ota_admin_data_t data)
{
    if (!admin_handle || !data || !data->url || !data->id || !data->token || !data->mac || !data->version)
        return;
    ota_admin_t admin = (ota_admin_t)admin_handle;
    memset(admin, 0, sizeof(ota_admin_s));
    char *p = admin->str_buf;
    memcpy(&admin->data, data, sizeof(ota_admin_data_s));

    admin->data.url = p;
    strcpy(p, data->url);
    p += strlen(data->url) + 1;

    admin->data.id = p;
    strcpy(p, data->id);
    p += strlen(data->id) + 1;

    admin->data.token = p;
    strcpy(p, data->token);
    p += strlen(data->token) + 1;

    admin->data.mac = p;
    strcpy(p, data->mac);
    p += strlen(data->mac) + 1;

    admin->data.version = p;
    strcpy(p, data->version);
    p += strlen(data->version) + 1;
}

void *ota_admin_create(ota_admin_data_t data)
{
    if (!data || !data->url || !data->id || !data->token || !data->mac || !data->version)
        return NULL;
    uint16_t str_len = 0;
    str_len += strlen(data->url);
    str_len += strlen(data->id);
    str_len += strlen(data->token);
    str_len += strlen(data->mac);
    str_len += strlen(data->version);
    ota_admin_t admin = NULL;
    admin = _malloc_(sizeof(ota_admin_s) + str_len + 5);
    if (!admin)
        admin = malloc(sizeof(ota_admin_s) + str_len + 5);
    if (!admin)
        return NULL;
    ota_admin_init(admin, data);
    return (void *)admin;
}
